<template>
  <div class="networkChart-container">
    <div class="networkChart-warp">
      <!--  -->
      <div
        class="eye-line"
        v-if="eyeLine.visible"
        :style="{ left: eyeLine.left + 'px' }"
      >
        <div class="value">{{ eyeLine.value }}</div>
      </div>
      <div class="networkChart-body" id="networkChartBody">
        <div class="chart-contant">
          <!--  -->
          <div class="header-container">
            <div class="left-warp">类型</div>
            <div class="right-warp">
              <div
                class="date-item"
                v-for="(item, index) in dateColumns"
                :key="index"
              >
                <!-- 年 -->
                <div class="year">{{ item.year }}年</div>
                <!-- 月 -->
                <div class="month-warp">
                  <!-- v-for="(month, monIdx) in item.month" -->
                  <div
                    class="month"
                    :id="`date_${item.year}_${month}`"
                    v-for="(month, monIdx) in 12"
                    :key="monIdx"
                  >
                    {{ month }}月
                  </div>
                </div>
              </div>
            </div>
          </div>
          <!--  -->
          <div class="body-container">
            <div
              class="line-char-item"
              :class="getTypeClassName(item.nodeType)"
              v-for="(item, index) in dataSource"
              :key="index"
            >
              <div class="type-warp">
                <div class="name">{{ item.name }}</div>
              </div>
              <div class="chart-warp"></div>
            </div>
            <!-- 节点 -->
            <div
              class="node-point"
              :class="getStatusClassName(node.status)"
              v-for="(node, index) in nodes"
              :key="`point_` + index"
              :style="{
                left: node.x - nodeRadius + 'px',
                top: node.y - nodeRadius + 'px',
              }"
              @mouseenter="handleMouseEnter($event, node)"
              @mouseleave="handleMouseLeave"
            >
              <div
                class="popover-container"
                :class="index % 2 == 0 ? 'bottom' : 'top'"
              >
                <div class="title">
                  （{{ node.rate || 0 }}%）{{ node.name }}
                </div>
                <div class="footer">
                  <div class="date start">
                    {{ node.planStartTime }}
                  </div>
                  <div class="date end">
                    {{ node.planEndTime }}
                  </div>
                </div>
              </div>
            </div>
            <!-- svg -->
            <svg class="canvas" width="100%" height="100%">
              <path
                :d="item.path"
                :stroke="item.color"
                fill="none"
                stroke-width="1"
                v-for="(item, index) in lines"
                :key="index"
                :marker-end="`url(#arrowhead_${index})`"
              />
              <g v-for="node in nodes" :key="node.uuid">
                <!-- 节点 -->
                <circle
                  v-if="node.x >= 0 && node.y >= 0"
                  :cx="node.x"
                  :cy="node.y"
                  :r="nodeRadius"
                  fill="rgba(0,0,0,0)"
                />
              </g>
              <defs>
                <!-- 箭头 -->
                <marker
                  v-for="(item, index) in lines"
                  :key="index"
                  :id="'arrowhead_' + index"
                  markerWidth="6"
                  markerHeight="6"
                  refX="6"
                  refY="3.5"
                  orient="auto"
                >
                  <polygon points="0 0, 6 3.5, 0 7" :fill="item.color" />
                </marker>
              </defs>
            </svg>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
//网络图
import { getNetworkDiagram } from "@/api/progressManage";
import { flatTreeData } from "@/utils/util";
import moment from "moment";
export default {
  props: {
    currentVersion: {
      type: Object,
      default: () => {
        return {};
      },
    },
  },
  data() {
    return {
      eyeLine: {
        visible: false,
        left: 0,
        value: "",
      },
      dataSource: [],
      dateColumns: [],
      nodeRadius: 10,
      nodes: [],
      nodesDemo: [
        { uuid: 1, x: 300, y: 86, lineColor: "#c34039", index: 1 },
        {
          uuid: 5,
          x: 300,
          y: 244,
          lineColor: "#358a73",
          index: 1,
        },
        {
          uuid: 9,
          x: 300,
          y: 402,
          lineColor: "#d4ac36",
          index: 1,
        },
        {
          uuid: 13,
          x: 300,
          y: 556,
          lineColor: "#2c5890",
          index: 1,
        },
      ],
      edges: [
        // {
        //   source: "4A616256412145508C0950C6DA5577BC",
        //   target: "C1041CAB10C44BEB80660132E49E1D44",
        // },
        // { source: 6, target: 11 },
        // { source: 9, target: 15 },
      ],
      //连接线数组
      lines: [],
    };
  },

  mounted() {
    this.getData();
  },

  methods: {
    handleMouseEnter(ev, node) {
      let { target } = ev;
      let { planEndTime } = node;
      let { nodeRadius } = this;
      this.eyeLine = {
        visible: true,
        left: target.offsetLeft + nodeRadius,
        value: planEndTime,
      };
    },
    handleMouseLeave() {
      this.eyeLine = {
        visible: false,
        left: 0,
        value: "",
      };
    },

    //查询table
    getData() {
      this.loading = true;
      let { status } = this.currentVersion;
      return new Promise((resolve, reject) => {
        getNetworkDiagram({
          showAll: 1,
          versionId: this.currentVersion?.uuid || "",
          nodeType: "scheduleType.01",
          proCode: "",
          orgCode: "",
          startDate: "",
          endDate: "",
          name: "",
          ganttChart: 1,
        })
          .then((res) => {
            let { data } = res;
            let source = data?.dataList ?? [];
            this.dataSource = this.arrayToTree(source);
            this.getColumnsData(source);
            let nodes = [];
            this.$nextTick((_) => {
              setTimeout((_) => {
                this.dataSource.forEach((v, i) => {
                  let { children, nodeType } = v;
                  let flatData = flatTreeData(children, "children").filter(
                    (v) => v.children.length == 0
                  );
                  let y = 86 + i * 156;
                  let lineColor = this.getLineColor(nodeType);
                  flatData.forEach((node) => {
                    nodes.push({
                      ...node,
                      y,
                      x: this.getNodeX(node),
                      lineColor,
                    });
                  });
                });
                this.nodes = nodes.filter((v) => v.x >= 0);
                //绘制连接线
                this.getLineData(data?.preList || []);
              }, 500);
            });
            //
            resolve();
          })
          .catch((_) => {
            this.lines = [];
            this.nodes = [];
            this.dataSource = [];
            reject();
          })
          .finally((_) => {
            this.loading = false;
          });
      });
    },

    //节点连接线
    getLineData(edges = []) {
      this.lines = [];
      edges.forEach((edge) => {
        let { source, target } = edge;
        let startEdge = this.nodes.find((v) => v.taskId == source);
        let endEdge = this.nodes.find((v) => v.taskId == target);
        if (startEdge && endEdge) {
          let path = this.pathData(startEdge, endEdge);
          this.lines.push({ path, color: startEdge.lineColor });
        }
      });
    },

    //连接线颜色
    getLineColor(type) {
      switch (type) {
        //报批报建计划
        case "scheduleType.02":
          return "#DE676C";

        //招采计划
        case "scheduleType.03":
          return "#DBB55C";

        //施工计划
        case "scheduleType.05":
          return "#5DBCB3";

        //设计计划
        case "scheduleType.04":
          return "#6995D2";

        //验收计划
        case "scheduleType.06":
          return "#9D8FCF";

        default:
          break;
      }
    },

    //计算节点的x坐标值
    getNodeX(node = {}) {
      let { planEndTime } = node;
      let year = moment(planEndTime).format("YYYY");
      let month = moment(planEndTime).format("M");
      let day = moment(planEndTime).format("D");
      let aboutDom = document.getElementById(`date_${year}_${month}`);
      let days = this.getDaysInMonth(planEndTime);
      let ratio = day / days;
      if (aboutDom) {
        return (
          (aboutDom?.offsetLeft || 0) + (aboutDom.clientWidth || 0) * ratio
        );
      } else {
        return -1;
      }
    },

    //获取指定日期字符串所在月份的天数
    getDaysInMonth(dateString) {
      const date = moment(dateString);
      return date.daysInMonth();
    },

    //计算顶部日期列表
    getColumnsData(data = []) {
      //计划开始日期
      let startDateList = data.map((v) => v.planStartTime).filter((v) => v);
      //计划结束日期
      let endDateList = data.map((v) => v.planEndTime).filter((v) => v);
      let dateArr = [...startDateList, ...endDateList];
      //排序&去重
      let sortDate = Array.from(new Set(this.sortDates(dateArr)));
      //
      this.dateColumns = this.getMonthRange(sortDate);
    },

    //获取月份列表
    getMonthRange(dates) {
      if (!dates || dates.length === 0) return [];

      // 将字符串格式的日期转换为 Date 对象
      const startDate = new Date(dates[0]);
      const endDate = new Date(dates[dates.length - 1]);

      // 计算两个月前的日期
      startDate.setMonth(startDate.getMonth() - 6);

      // 计算两个月后的日期
      endDate.setMonth(endDate.getMonth() + 6);

      const result = [];
      const yearMonthsMap = {};

      // 遍历从 startDate 到 endDate 之间的每个月
      let current = new Date(startDate);
      while (current <= endDate) {
        const year = current.getFullYear();
        const month = current.getMonth() + 1; // getMonth 返回的月份是 0-11，所以需要加 1

        if (!yearMonthsMap[year]) {
          yearMonthsMap[year] = [];
        }

        if (!yearMonthsMap[year].includes(month)) {
          yearMonthsMap[year].push(month);
        }

        // 移动到下一个月
        current.setMonth(current.getMonth() + 1);
      }

      // 将 yearMonthsMap 转换为所需的格式
      for (const year in yearMonthsMap) {
        result.push({
          year: parseInt(year),
          month: yearMonthsMap[year],
        });
      }

      return result;
    },

    //日期排序
    sortDates(dateArray) {
      return dateArray.sort((a, b) => {
        return new Date(a) - new Date(b);
      });
    },

    //
    arrayToTree(array) {
      const map = {};
      const roots = [];
      // 首先将所有节点存储到一个 Map 中，并初始化 children 数组
      array.forEach((item) => {
        map[item.uuid] = { ...item, children: [] };
      });

      // 构建树形结构
      array.forEach((item) => {
        const node = map[item.uuid];
        if (item.path === null) {
          // 如果没有父节点，说明是根节点
          roots.push(node);
        } else {
          let paths = item.path.split(",");
          paths.forEach((v) => {
            // 否则，将其添加到父节点的 children 数组中
            if (map[v]) {
              map[v].children.push(node);
            }
          });
        }
      });
      return roots;
    },

    //状态样式
    getStatusClassName(status) {
      switch (Number(status)) {
        //未开始
        case 0:
          return "wks";

        //延期未开始
        case 1:
          return "yqwks";

        //进行中
        case 2:
          return "ing";

        //延期已开始
        case 3:
          return "yqyks";

        //延期未完成
        case 4:
          return "yqwwc";

        //已完成
        case 5:
          return "ywc";

        //延期完成
        case 6:
          return "yqwc";

        default:
          return "wks";
      }
    },

    //类型样式
    getTypeClassName(type = 1) {
      switch (type) {
        //报批报建计划
        case "scheduleType.02":
          return "bpbj";

        //招采计划
        case "scheduleType.03":
          return "zcjh";

        //施工计划
        case "scheduleType.05":
          return "sgjh";

        //设计计划
        case "scheduleType.04":
          return "sjjh";

        //验收计划
        case "scheduleType.06":
          return "ysjh";

        default:
          break;
      }
    },

    pathData(startEdge, endEdge) {
      let { nodeRadius } = this;

      let startX = startEdge.x;
      let startY = startEdge.y;

      let endX = endEdge.x;
      let endY = endEdge.y;

      // 判断起点和终点的相对位置，决定连接方向
      const isStartHigher = startY < endY;

      // 计算连接点的位置（节点的上方或下方）
      const startConnectY = isStartHigher
        ? startY + nodeRadius // 如果起点更高，从下方连接
        : startY - nodeRadius; // 如果起点更低，从上方连接

      const endConnectY = isStartHigher
        ? endY - nodeRadius // 如果终点更低，从上方连接
        : endY + nodeRadius; // 如果终点更高，从下方连接

      // 计算中间点的 Y 坐标
      const middleY = (startConnectY + endConnectY) / 2;

      let { vertical } = this.getLineDir(startEdge, endEdge);

      let mY = endY - (vertical ? -50 : 50);

      // 构建路径
      return `M ${startX} ${startConnectY}
                L ${startX} ${mY}
                L ${endX} ${mY}
                L ${endX} ${endConnectY}`;
    },

    // 检查线段是否与节点碰撞
    getLineDir(startEdge, endEdge) {
      //结束点在起始点的水平方向 horizontal ? "左侧" : "右侧"
      let horizontal = startEdge.x > endEdge.x;
      //结束点在起始点的垂直方向 vertical ? "上侧" : "下侧"
      let vertical = startEdge.y > endEdge.y;
      return {
        horizontal,
        vertical,
      };
    },
  },
};
</script>

<style lang="less" scoped>
.networkChart-container {
  width: 100%;
  height: 100%;
  background: #fff;
  box-sizing: border-box;
  padding: 16px;
  overflow: hidden;
  @border-color: #dcdee2;
  @left-width: 134px;
  position: relative;

  .networkChart-warp {
    width: 100%;
    height: 100%;
    overflow: hidden;
    position: relative;
    &::before {
      content: "";
      position: absolute;
      width: 1px;
      top: 0;
      left: @left-width;
      height: 100%;
      background: @border-color;
      z-index: 999;
    }
    .eye-line {
      content: "";
      position: absolute;
      width: 1px;
      height: 100%;
      top: 0;
      left: 547px;
      border-left: 1px dashed #1890ff;
      z-index: 9999;
      pointer-events: none;
      .value {
        position: absolute;
        display: flex;
        flex-wrap: nowrap;
        white-space: nowrap;
        font-size: 12px;
        padding: 2px 4px;
        border-radius: 2px;
        top: 2px;
        left: 2px;
        background: rgba(0, 0, 0, 0.5);
        color: #fff;
        backdrop-filter: blur(10px);
      }
    }
  }
  .networkChart-body {
    width: 100%;
    height: 100%;
    border: 1px solid @border-color;
    overflow: auto;
    .chart-contant {
      width: fit-content;
      min-width: 100%;
      .header-container {
        height: 52px;
        border-bottom: 1px solid @border-color;
        display: flex;
        width: fit-content;
        min-width: 100%;
        position: sticky;
        top: 0;
        background: #fafafa;
        z-index: 999;
        .left-warp {
          width: @left-width;
          height: 100%;
          position: sticky;
          left: 0;
          background: #fafafa;
          display: flex;
          align-items: center;
          justify-content: center;
          flex-shrink: 0;
          border-right: 1px solid @border-color;
        }
        .right-warp {
          width: fit-content;
          height: 100%;
          display: flex;
          flex-wrap: nowrap;
          color: #333333;
          font-size: 12px;
          flex: 1;
          .date-item {
            display: flex;
            flex-direction: column;
            flex: 1;
            flex-wrap: nowrap;
            &:not(:last-child) {
              border-right: 1px solid @border-color;
            }
            .year {
              flex: 1;
              flex-shrink: 0;
              border-bottom: 1px solid @border-color;
              padding-left: 12px;
              display: flex;
              align-items: center;
            }
            .month-warp {
              flex: 1;
              flex-shrink: 0;
              display: flex;
              flex-wrap: nowrap;
              .month {
                min-width: 44px;
                flex: 1;
                display: flex;
                align-items: center;
                justify-content: center;
                &:not(:last-child) {
                  border-right: 1px solid @border-color;
                }
              }
            }
          }
        }
      }
      .body-container {
        padding: 14px 0;
        position: relative;
        .node-point {
          position: absolute;
          width: 20px;
          height: 20px;
          background: red;
          z-index: 9;
          cursor: pointer;
          border-radius: 50%;
          .popover-container {
            position: absolute;
            width: 130px;
            background: #ffffff;
            box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.1);
            border-radius: 4px 4px 4px 4px;
            left: 50%;
            transform: translate(-50%, 0);
            padding-bottom: 3px;
            &.bottom {
              top: 30px;
              &:before {
                content: "";
                position: absolute;
                left: 50%;
                transform: translateX(-50%) rotate(45deg);
                width: 8px;
                height: 8px;
                background: transparent;
                border-style: solid;
                border-width: 4px;
                border-color: #fff transparent transparent #fff;
                top: -4px;
              }
            }
            &.top {
              bottom: calc(100% + 18px);
              &:before {
                content: "";
                position: absolute;
                left: 50%;
                transform: translateX(-50%) rotate(45deg);
                width: 8px;
                height: 8px;
                background: transparent;
                border-style: solid;
                border-width: 4px;
                border-color: transparent #fff #fff transparent;
                bottom: -4px;
              }
            }
            padding: 10px 12px;
            max-width: 180px;
            font-size: 12px;
            .title {
              color: #333;
            }
            .date {
              color: #666;
              display: flex;
              align-items: center;
              &:before {
                content: "";
                display: flex;
                width: 14px;
                height: 14px;
                margin-right: 6px;
              }
              &.start {
                margin-bottom: 2px;
                &:before {
                  background: url("~@/assets/img/icon_16.png");
                  background-size: 100% 100%;
                }
              }
              &.end {
                &:before {
                  background: url("~@/assets/img/icon_17.png");
                  background-size: 100% 100%;
                }
              }
            }
          }
          &:hover {
            z-index: 99;
          }
          //未开始
          &.wks {
            border: 3px solid #8c8c8c;
            background: #fff;
          }

          //延期未开始
          &.yqwks {
            border: 3px solid #ff3f4a;
            background: #fff;
          }
          //进行中
          &.ing {
            border: none;
            background: url("~@/assets/img/icon_9.png");
            background-size: 100% 100%;
          }
          //延期已开始
          &.yqyks {
            border: none;
            background: url("~@/assets/img/icon_10.png");
            background-size: 100% 100%;
          }
          //延期未完成
          &.yqwwc {
            border: none;
            background: url("~@/assets/img/icon_11.png");
            background-size: 100% 100%;
          }
          //已完成
          &.ywc {
            border: none;
            background: url("~@/assets/img/icon_12.png");
            background-size: 100% 100%;
          }
          //延期完成
          &.yqwc {
            border: none;
            background: url("~@/assets/img/icon_13.png");
            background-size: 100% 100%;
          }
        }
        svg {
          position: absolute;
          top: 0;
          left: 0;
        }
        .line-char-item {
          height: 156px;
          display: flex;

          //   &:not(:last-child) {
          //     margin-bottom: 8px;
          //   }
          .type-warp {
            flex-shrink: 0;
            width: @left-width;
            height: 100%;
            position: sticky;
            left: 0;
            display: flex;
            align-items: center;
            justify-content: center;
            background: #fff;
            z-index: 99;
            border-bottom: 8px solid #fff;
            .name {
              display: flex;
              align-items: center;
              justify-content: center;
              width: 100px;
              height: 40px;
              background: linear-gradient(180deg, #ffc711 0%, #d75900 100%);
              border-radius: 6px 6px 6px 6px;
              font-size: 14px;
              color: #fff;
            }
          }
          .chart-warp {
            flex: 1;
            height: 148px;
            background: #f5f9ff;
            position: relative;
            &::after {
              content: "";
              position: absolute;
              height: 6px;
              width: 100%;
              left: 0;
              top: 50%;
              transform: translateY(-50%);
            }
          }
          //报批报建计划
          &.bpbj {
            .type-warp {
              .name {
                background: linear-gradient(180deg, #ff3f4a 0%, #99262c 100%);
              }
            }
            .chart-warp {
              &::after {
                background: #de676c;
              }
            }
          }
          //招采计划
          &.zcjh {
            .type-warp {
              .name {
                background: linear-gradient(180deg, #ffc711 0%, #d75900 100%);
              }
            }
            .chart-warp {
              &::after {
                background: #dbb55c;
              }
            }
          }
          //施工计划
          &.sgjh {
            .type-warp {
              .name {
                background: linear-gradient(180deg, #00c1b6 0%, #097d47 100%);
              }
            }
            .chart-warp {
              &::after {
                background: #5dbcb3;
              }
            }
          }
          //设计计划
          &.sjjh {
            .type-warp {
              .name {
                background: linear-gradient(180deg, #1890ff 0%, #004db9 100%);
              }
            }
            .chart-warp {
              &::after {
                background: #6995d2;
              }
            }
          }
          //验收计划
          &.ysjh {
            .type-warp {
              .name {
                background: linear-gradient(180deg, #bbaaf6 0%, #6f54cc 100%);
              }
            }
            .chart-warp {
              &::after {
                background: #9d8fcf;
              }
            }
          }
        }
      }
    }
  }
}
</style>
